March 07, 2021
늘 프로젝트 할 때마다 모달은 필수적으로 구현해야하는 UI 입니다.
이전 프로젝트를 진행할 때는 여러모로 부족했기 때문에 모달을 굉장히 더럽고(..) 재사용성이 떨어지게 구현했었습니다..그래서 현재 프로젝트를 진행할 때 이번에는 정말 모달을 딱 하나의 컴포넌트와 하나의 훅스로 사용할 수 있도록 만들게쒀!! 라고 다짐했고 제가 어떻게 모달을 구현했는지 소개해보고자 합니다.
일단 react-dom에서 제공해주는 Portal
을 사용합니다.
모달은 하나의 컴포넌트와 하나의 커스텀 훅스를 통해서 구현됩니다.
먼저 코드를 보기전에 간단하게 로직을 설명하면 아래와 같습니다.
<Modal><컴포넌트/></Modal>
이렇게 삽입해줍니다. 그러면 이제 코드를 보겠습니다.
import { useState, useEffect, useCallback } from 'react'
const useModal = (opened: boolean = false) => {
const [showModal, setShowModal] = useState<boolean>(opened)
useEffect(() => {
setShowModal(opened)
}, [opened])
useEffect(() => {
if (showModal) {
document.body.style.overflowY = 'hidden'
} else {
document.body.style.overflowY = 'auto'
}
}, [showModal])
const modalHandler = useCallback(() => {
setShowModal(!showModal)
}, [showModal])
return [showModal, modalHandler] as const
}
export default useModal
opened
라는 boolean 변수를 받습니다. opened 가 true라면 showModal
의 디폴트 상태가 true가 됩니다. document.body.style.overflowY
를 변경해줍니다. open 된 경우에는 hidden
으로 하여 뒷 화면의 스크롤을 막고 , close되면 원상복구 해줍니다. import React from 'react'
import ReactDOM from 'react-dom'
import { useElement } from 'hooks'
import styled from '@emotion/styled'
interface Props {
/** Modal's inner contents */
children: React.ReactNode
/** show modal or not */
showModal: boolean
/** close / open modal handler */
modalHandler: () => void
}
const Modal = ({
children,
showModal,
modalHandler,
}: Props): React.ReactElement => {
const root = useElement('modal-root')
return showModal && root
? ReactDOM.createPortal(
<>
<Overlay onClick={modalHandler} />
{children}
</>,
root
)
: null
}
const Overlay = styled.div`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
z-index: 6000;
`
export default Modal
document~
에 바로 접근할 수 없습니다. 따라서 useElement
라는 훅스를 사용하여 원하는 document에 접근합니다. 자세한건 이 포스팅을 참고해주시면 감사하겠습니다.showModal === true
일 때만 Portal을 통해 모달 컴포넌트를 붙여줍니다.import { useModal } from 'hooks';
import { Modal } from 'components/ui';
import { baseModalStyle } from 'common/style'
import styled from 'styled-components';
const Example=()=>{
const [showModal, modalHanlder] = useModal();
return(
<div>
....
<Modal>
<MainContents/>
</Modal>
<div>
);
};
const MainContents = styled.div`
...
${baseModalStyle}
`;
export default Example;
baseModalStyle
은 메인 컨텐츠를 전체 화면의 정 가운데에 놓기위한 css 코드입니다. 모든 Main Contents마다 똑같은 스타일링을 반복하는게 비효율적이어서 만들었습니다.import { css } from '@emotion/react'
export const baseModalStyle = css`
position: fixed;
top: 50%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, -50%);
z-index: 7000;
`
이렇게 간단하게 모달을 구현했습니다. 현재 프로젝트에서 굉장히 요긴하게 잘 쓰고 있습니다 ㅎㅎ 언제나 궁금하신 점이나 지적은 환영입니다.